package cn.itcast.search.service.impl;

import cn.itcast.feign.client.ItemClient;
import cn.itcast.hmall.dto.common.PageDTO;
import cn.itcast.hmall.dto.common.ResultDTO;
import cn.itcast.hmall.dto.item.SearchItemDTO;
import cn.itcast.hmall.dto.search.SearchReqDTO;
import cn.itcast.hmall.pojo.item.Item;
import cn.itcast.hmall.pojo.item.ItemDoc;
import cn.itcast.search.service.SearchService;
import com.alibaba.fastjson.JSON;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ItemClient itemClient;
    @Autowired
    private RestHighLevelClient client;

    @Override
    public ResultDTO addES() {
        try {
            SearchItemDTO searchItemDTO = new SearchItemDTO();
            searchItemDTO.setSize(1000);
            boolean flag = false;
            int i = 1;

            while (true) {
                BulkRequest bulkRequest = new BulkRequest("item");
                searchItemDTO.setPage(i);
                PageDTO<Item> goodsByPage = itemClient.findGoodsByPage(searchItemDTO);
                List<Item> list = goodsByPage.getList();

                if (list.size() == 0) {
                    break;
                }
                List<Item> list1 = goodsByPage.getList();
                for (Item item : list1) {
                    IndexRequest request = new IndexRequest();
                    request.id(item.getId() + "");
                    ItemDoc itemDoc = new ItemDoc(item);
                    //设置文档内容
                    request.source(JSON.toJSONString(itemDoc), XContentType.JSON);
                    //将添加文档的请求 加入到批量处理请求中
                    bulkRequest.add(request);
                }
                i++;
                BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
                flag = bulk.hasFailures();
            }
            if (flag) {
                return ResultDTO.ok();
            }
            return ResultDTO.error("添加失败");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //搜索栏自动补全
    @Override
    public List<String> getSuggestions(String prefix) {
        try {
            //1.创建搜索请求
            SearchRequest request = new SearchRequest("item");
            //准备DSL
            request.source().suggest(new SuggestBuilder()
                    .addSuggestion(
                            "mySuggestion",//自定义的查询名称
                            SuggestBuilders.completionSuggestion("suggestion")
                                    .prefix(prefix)//匹配前缀为key的词条
                                    .skipDuplicates(true) //去除重复的词条
                                    .size(10) //保留10条
                    ));
            //发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //查询suggest结果
            Suggest suggest = response.getSuggest();
            //根据补全擦互训名称,获取不全结果
            CompletionSuggestion mySuggestion = suggest.getSuggestion("mySuggestion");
            List<CompletionSuggestion.Entry.Option> options = mySuggestion.getOptions();
            List<String> list = new ArrayList<>(options.size());
            for (CompletionSuggestion.Entry.Option option : options) {
                String s = option.getText().toString();
                list.add(s);
            }
            return list;
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }

    /**
     * 过滤功能
     *
     * @param searchReqDTO
     * @return
     */
    @Override
    public Map<String, List<String>> getFilters(SearchReqDTO searchReqDTO) {
        try {
            //1.创建搜索请求
            SearchRequest request = new SearchRequest("item");
            buildBasicQuery(searchReqDTO, request);
            SearchSourceBuilder source = request.source();
            source.size(0);
            //设置 聚合请求条件
            source.aggregation(AggregationBuilders
                    .terms("brandAgg")
                    .field("brand")
                    .size(100)
            );
            source.aggregation(AggregationBuilders
                    .terms("categoryAgg")
                    .field("category")
                    .size(100)
            );
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            Aggregations aggregations = response.getAggregations();
            List<String> brandAgg = getAggByName(aggregations, "brandAgg");
            List<String> categoryAgg = getAggByName(aggregations, "categoryAgg");
            Map<String, List<String>> maps = new HashMap<>();
            maps.put("category", categoryAgg);
            maps.put("brand", brandAgg);
            return maps;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //基本搜索功能
    @Override
    public PageDTO<ItemDoc> findGoods(SearchReqDTO searchReqDTO) {
        try {
            //1.创建搜索请求
            SearchRequest request = new SearchRequest("item");
            //准备DSL
            buildBasicQuery(searchReqDTO, request);
            //2.1 query
            //构建条件
            SearchSourceBuilder source = request.source();
            if (searchReqDTO.getSortBy().equals("sold")) {
                //按照销量降序
                source.sort("sold", SortOrder.DESC);
            }
            if (searchReqDTO.getSortBy().equals("price")) {
                //按照价格升序
                source.sort("price", SortOrder.ASC);
            }

            //分页
            source.from((searchReqDTO.getPage() - 1) * searchReqDTO.getSize());
            source.size(searchReqDTO.getSize());

            SearchResponse search = client.search(request, RequestOptions.DEFAULT);
            return getPageResult(search);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 上架商品
    @Override
    public void downGoods(Long id) {
        try {
            //根据id查询商品信息
            Item goodsById = itemClient.findGoodsById(id.toString());
            //转换文档
            ItemDoc itemDoc = new ItemDoc(goodsById);
            //准备respuest对象
            IndexRequest request = new IndexRequest("item").id(itemDoc.getId().toString());
            //准备JSON文档
            request.source(JSON.toJSONString(itemDoc), XContentType.JSON);
            //发送请求
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
    //监听下架商品
    @Override
    public void deleteGoods(Long id) {
        try {
            //1.准备Request
            DeleteRequest deleteRequest = new DeleteRequest("item",id.toString());
            //2.发送请求
            client.delete(deleteRequest,RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("删除失败");
        }
    }

    //解析响应
    private PageDTO<ItemDoc> getPageResult(SearchResponse search) {
        // 解析响应
        SearchHits hits = search.getHits();
        //获得总条数
        long total = hits.getTotalHits().value;
        //文档数组
        SearchHit[] hits1 = hits.getHits();
        List<ItemDoc> itemDocs = new ArrayList<>();

        //遍历
        for (SearchHit hit : hits1) {
            //获取文档source
            String sourceAsString = hit.getSourceAsString();
            //反序列化
            ItemDoc itemDoc = JSON.parseObject(sourceAsString, ItemDoc.class);
            // 获取高亮结果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();

            if (highlightFields != null) {
                //根据字段名获取高亮结果
                HighlightField name = highlightFields.get("name");
                if (name != null) {
                    itemDoc.setName(name.getFragments()[0].toString());
                }
            }

            itemDocs.add(itemDoc);
        }
        PageDTO<ItemDoc> dto = new PageDTO<>();
        dto.setList(itemDocs);
        dto.setTotal(total);
        return dto;
    }

    /**
     * @param aggregations 集合结果对象
     * @param aggName      自定义的聚合名称
     * @return
     */
    private List<String> getAggByName(Aggregations aggregations, String aggName) {
        // 6.2 获取指定聚合结果
        Terms aggResult = aggregations.get(aggName);
        //6.3 获取结果中的分组信息 buckets
        List<? extends Terms.Bucket> buckets = aggResult.getBuckets();
        List<String> lists = new ArrayList<>();

        for (Terms.Bucket bucket : buckets) {
            //获取每个分组的key 加入到集合
            String keyAsString = bucket.getKeyAsString();
            lists.add(keyAsString);
        }
        return lists;
    }

    //过滤请求的方法
    private void buildBasicQuery(SearchReqDTO searchReqDTO, SearchRequest request) {
        //构建boole
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        String key = searchReqDTO.getKey();
        if (key == null || "".equals(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
            //8.高亮
            request.source().highlighter(new HighlightBuilder().field("name").
                    //关闭高亮字段检索
                            requireFieldMatch(false));
        }
        //根据东西
        if (searchReqDTO.getCategory() != null && !searchReqDTO.getCategory().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("category", searchReqDTO.getCategory()));
        }
        //根据品牌
        if (searchReqDTO.getBrand() != null && !searchReqDTO.getBrand().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("brand", searchReqDTO.getBrand()));
        }
        //价格
        if (searchReqDTO.getMaxPrice() != null && searchReqDTO.getMinPrice() != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("price")
                    .gte(searchReqDTO.getMinPrice())
                    .lte(searchReqDTO.getMaxPrice()));
        }
        // 算分控制
        FunctionScoreQueryBuilder isAD = QueryBuilders.functionScoreQuery(
                //原始查询,相关性分数查询
                boolQuery,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        //其中一个function score 元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                //过滤条件
                                QueryBuilders.termQuery("isAD", true),
                                //算分函数
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }
        );
        request.source().query(isAD);
    }
}
